using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NFreeMarker.Ext.Beans;

namespace NFreeMarker.Template
{
    /// <summary>
    /// <para>A convenient implementation of a list. This
    /// object implements <see cref="ITemplateSequenceModel"/>, using an underlying
    /// <see cref="List{T}"/> implementation.</para>
    /// 
    /// <para>A <tt>SimpleSequence</tt> can act as a cache for a
    /// <see cref="ITemplateCollectionModel"/> e.g. one that gets data from a
    /// database.  When passed a <see cref="ITemplateCollectionModel"/> as an
    /// argument to its constructor, the <see cref="SimpleSequence"/> immediately
    /// copies all the elements and discards the <see cref="ITemplateCollectionModel"/>.</para>
    /// 
    /// <para>This class is thread-safe if you don't call the <see cref="Add(object)"/> method after you
    /// have made the object available for multiple threads.</para>
    /// </summary>
    /// 
    /// <remarks>
    /// As of 2.0, this class is unsynchronized by default.
    /// To obtain a synchronized wrapper, call the <see cref="SynchronizedWrapper"/> method.
    /// </remarks>
    /// 
    /// <seealso cref="SimpleHash"/>
    /// <seealso cref="SimpleScalar"/>
    public class SimpleSequence : WrappingTemplateModel, ITemplateSequenceModel //, Serializable 
    {
        /**
         * @serial The <tt>List</tt> that this <tt>SimpleSequence</tt> wraps.
         */
        private readonly List<object> _list;
        private List<object> _unwrappedList;

        /// <summary>
        /// Constructs an empty simple sequence that will use the the default object
        /// wrapper set in <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/>.
        /// </summary>
        public SimpleSequence()
            : this((IObjectWrapper) null)
        {
        }

        /// <summary>
        /// Constructs an empty simple sequence with preallocated capacity and using
        /// the default object wrapper set in 
        /// <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/>.
        /// </summary>
        /// <param name="capacity">The preallocated capacity.</param>
        public SimpleSequence(int capacity)
        {
            _list = new List<object>(capacity);
        }

        /// <summary>
        /// Constructs a simple sequence that will contain the elements
        /// from the specified <see cref="IEnumerable"/> and will use the the default 
        /// object wrapper set in <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/>.
        /// </summary>
        /// <param name="collection">The collection containing initial values. Note that a
        /// copy of the collection is made for internal use.</param>
        public SimpleSequence(IEnumerable collection)
            : this(collection, null)
        {
        }

        /// <summary>
        /// Constructs a simple sequence from the passed collection model using the
        /// default object wrapper set in
        /// <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/>.
        /// </summary>
        /// <param name="tcm">The collection model containing initial values.</param>
        public SimpleSequence(ITemplateCollectionModel tcm)
        {
            var alist = new List<object>();
            for (ITemplateModelIterator it = tcm.Iterator(); it.HasNext();)
            {
                alist.Add(it.Next());
            }
            alist.TrimExcess();
            _list = alist;
        }

        /// <summary>
        /// Constructs an empty simple sequence using the specified object wrapper.
        /// </summary>
        /// <param name="wrapper">The object wrapper to use to wrap objects into
        /// <see cref="ITemplateModel"/> instances. If null, the default wrapper set in
        /// <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/> is
        /// used. </param>
        public SimpleSequence(IObjectWrapper wrapper)
            : base(wrapper)
        {
            _list = new List<object>();
        }

        /**
         * Constructs a simple sequence that will contain the elements
         * from the specified {@link Collection} and will use the specified object
         * wrapper.
         * @param collection the collection containing initial values. Note that a
         * copy of the collection is made for internal use.
         * @param wrapper The object wrapper to use to wrap objects into
         * {@link TemplateModel} instances. If null, the default wrapper set in 
         * <see cref="WrappingTemplateModel.SetDefaultObjectWrapper"/> is
         * used.
         */
        public SimpleSequence(IEnumerable collection, IObjectWrapper wrapper)
            : base(wrapper)
        {
            _list = new List<object>(collection.Cast<object>());
        }

        /// <summary>
        /// Adds an arbitrary object to the end of this <see cref="SimpleSequence"/>.
        /// If the object itself does not implement the <see cref="ITemplateModel"/>
        /// interface, it will be wrapped into an appropriate adapter on the first
        /// call to <see cref="Get(int)"/>.
        /// </summary>
        /// <param name="obj">The object to be added.</param>
        public virtual void Add(object obj)
        {
            _list.Add(obj);
            _unwrappedList = null;
        }

        /**
         * Adds a boolean to the end of this <tt>SimpleSequence</tt>, by 
         * coercing the boolean into {@link TemplateBooleanModel#TRUE} or 
         * {@link TemplateBooleanModel#FALSE}.
         *
         * @param b the boolean to be added.
         */
        public void Add(bool b)
        {
            Add(b ? TemplateBooleanModel.True : TemplateBooleanModel.False);
        }

        /**
         * Note that this method creates and returns a deep-copy of the underlying list used
         * internally. This could be a gotcha for some people
         * at some point who want to alter something in the data model,
         * but we should maintain our immutability semantics (at least using default SimpleXXX wrappers) 
         * for the data model. It will recursively unwrap the stuff in the underlying container. 
         */
        public virtual List<object> ToList()
        {
            if (_unwrappedList == null)
            {
                var result = new List<object>();
                BeansWrapper bw = BeansWrapper.GetDefaultInstance();
                foreach (object elem in _list)
                {
                    if (elem is ITemplateModel)
                    {
                        result.Add(bw.Unwrap((ITemplateModel) elem));
                    }
                    else
                    {
                        result.Add(elem);
                    }
                }
                _unwrappedList = result;
            }
            return _unwrappedList;
        }

        /**
         * @return the specified index in the list
         */
        public virtual ITemplateModel Get(int i)
        {
            try
            {
                object value = _list[i];
                if (value is ITemplateModel)
                {
                    return (ITemplateModel) value;
                }
                ITemplateModel tm = Wrap(value);
                _list[i] = tm;
                return tm;
            }
            catch (IndexOutOfRangeException)
            {
                return null;
                //            throw new TemplateModelException(i + " out of bounds [0, " + list.size() + ")");
            }
        }

        public virtual int Size()
        {
            return _list.Count;
        }

        /**
         * @return a synchronized wrapper for list.
         */
        public SimpleSequence SynchronizedWrapper()
        {
            return new SynchronizedSequence(this);
        }

        public override string ToString()
        {
            return _list.ToString();
        }

        private class SynchronizedSequence : SimpleSequence
        {
            private readonly SimpleSequence _sequence;

            public SynchronizedSequence(SimpleSequence sequence)
            {
                _sequence = sequence;
            }

            public override void Add(object obj)
            {
                lock (_sequence)
                {
                    _sequence.Add(obj);
                }
            }

            public override ITemplateModel Get(int i)
            {
                lock (_sequence)
                {
                    return _sequence.Get(i);
                }
            }

            public override int Size()
            {
                lock (_sequence)
                {
                    return _sequence.Size();
                }
            }

            public override List<object> ToList()
            {
                lock (_sequence)
                {
                    return _sequence.ToList();
                }
            }
        }
    }
}